package wiki.xsx.core.handler;

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import wiki.xsx.core.util.ConvertUtil;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
 * 位图类型助手
 * @author xsx
 * @date 2019/4/22
 * @since 1.8
 */
public class BitmapTypeHandler {
    /**
     * 字符串模板
     */
    private StringRedisTemplate stringRedisTemplate;

    private static final StringRedisSerializer keySerializer = new StringRedisSerializer(StandardCharsets.UTF_8);

    /**
     * 位图类型助手构造
     * @param stringRedisTemplate 字符串模板
     */
    private BitmapTypeHandler(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 获取实例
     * @param stringRedisTemplate 字符串模板
     * @return 返回实例
     */
    public static BitmapTypeHandler getInstance(StringRedisTemplate stringRedisTemplate) {
        return new BitmapTypeHandler(stringRedisTemplate);
    }

    /**
     * 设置位
     * <p>SETBIT key offset value</p>
     * @since redis 2.2.0
     * @param key 键
     * @param offset 偏移量
     * @param bit 位值,true=1,false=0
     * @return 返回布尔值
     */
    public Boolean set(String key, Long offset, boolean bit) {
        return this.stringRedisTemplate.opsForValue().setBit(key, offset, bit);
    }

    /**
     * 获取位
     * <p>GETBIT key offset</p>
     * @since redis 2.2.0
     * @param key 键
     * @param offset 偏移量
     * @return 返回布尔值
     */
    public Boolean get(String key, Long offset) {
        return this.stringRedisTemplate.opsForValue().getBit(key, offset);
    }

    /**
     * 位长度
     * @since redis 2.2.0
     * @param key 键
     * @return 返回位的总长度
     */
    public Long bitLength(String key) {
        String s = this.stringRedisTemplate.opsForValue().get(key);
        if (s==null) {
            return 0L;
        }
        return (long) (s.length() << 3);
    }

    /**
     * 二进制
     * @since redis 2.2.0
     * @param key 键
     * @return 返回二进制字符串
     */
    public String binary(String key) {
        String value = this.stringRedisTemplate.opsForValue().get(key);
        if (value==null) {
            return null;
        }
        char[] chars = value.toCharArray();
        int count = chars.length;
        int length = count<<3;
        int bit = 8;
        StringBuilder builder = new StringBuilder(length);
        for (char aChar : chars) {
            for (int j = bit - 1; j >= 0; j--) {
                builder.append(aChar >>> j & 1);
            }
            builder.append(' ');
        }
        return builder.substring(0, length+count-1);
    }

    /**
     * 统计
     * <p>BITCOUNT key [start end]</p>
     * @since redis 2.6.0
     * @param key 键
     * @return 返回统计总数
     */
    public Long count(String key) {
        final String keyCase = key;
        return this.stringRedisTemplate.execute(
                new RedisCallback<Long>() {
                    @Override
                    public Long doInRedis(RedisConnection connection) throws DataAccessException {
                        return connection.bitCount(
                                keySerializer.serialize(keyCase)
                        );
                    }
                }, true
        );
    }

    /**
     * 统计
     * <p>BITCOUNT key [start end]</p>
     * @since redis 2.6.0
     * @param key 键
     * @param startIndex 开始字节索引
     * @param endIndex 结束字节索引
     * @return 返回统计总数
     */
    public Long count(String key, Long startIndex, Long endIndex) {
        final String keyCase = key;
        final long startByteIndex = startIndex << 3;
        final long endByteIndex = endIndex << 3;
        return this.stringRedisTemplate.execute(
                new RedisCallback<Long>() {
                    @Override
                    public Long doInRedis(RedisConnection connection) throws DataAccessException {
                        return connection.bitCount(
                                keySerializer.serialize(keyCase),
                                startByteIndex,
                                endByteIndex
                        );
                    }
                }, true
        );
    }

    /**
     * 逻辑与
     * <p>BITOP operation destkey key [key ...]</p>
     * @since redis 2.6.0
     * @param storeKey 存储键
     * @param keys 键
     * @return 返回最长字符串长度
     */
    public Long bitOpWithAnd(String storeKey, String ...keys) {
        final String storeKeyCase = storeKey;
        final byte[][] keyBytes = ConvertUtil.toArray(keySerializer, keys);
        return this.stringRedisTemplate.execute(
                new RedisCallback<Long>() {
                    @Override
                    public Long doInRedis(RedisConnection connection) throws DataAccessException {
                        return connection.bitOp(
                                RedisStringCommands.BitOperation.AND,
                                keySerializer.serialize(storeKeyCase),
                                keyBytes
                        );
                    }
                }, true);
    }

    /**
     * 逻辑或
     * <p>BITOP operation destkey key [key ...]</p>
     * @since redis 2.6.0
     * @param storeKey 存储键
     * @param keys 键
     * @return 返回最长字符串长度
     */
    public Long bitOpWithOr(String storeKey, String ...keys) {
        final String storeKeyCase = storeKey;
        final byte[][] keyBytes = ConvertUtil.toArray(keySerializer, keys);
        return this.stringRedisTemplate.execute(
                new RedisCallback<Long>() {
                    @Override
                    public Long doInRedis(RedisConnection connection) throws DataAccessException {
                        return connection.bitOp(
                                RedisStringCommands.BitOperation.OR,
                                keySerializer.serialize(storeKeyCase),
                                keyBytes
                        );
                    }
                }, true);
    }

    /**
     * 逻辑异或
     * <p>BITOP operation destkey key [key ...]</p>
     * @since redis 2.6.0
     * @param storeKey 存储键
     * @param keys 键
     * @return 返回最长字符串长度
     */
    public Long bitOpWithXor(String storeKey, String ...keys) {
        final String storeKeyCase = storeKey;
        final byte[][] keyBytes = ConvertUtil.toArray(keySerializer, keys);
        return this.stringRedisTemplate.execute(
                new RedisCallback<Long>() {
                    @Override
                    public Long doInRedis(RedisConnection connection) throws DataAccessException {
                        return connection.bitOp(
                                RedisStringCommands.BitOperation.XOR,
                                keySerializer.serialize(storeKeyCase),
                                keyBytes
                        );
                    }
                }, true);
    }

    /**
     * 逻辑非
     * <p>BITOP operation destkey key [key ...]</p>
     * @since redis 2.6.0
     * @param storeKey 存储键
     * @param keys 键
     * @return 返回最长字符串长度
     */
    public Long bitOpWithNot(String storeKey, String ...keys) {
        final String storeKeyCase = storeKey;
        final byte[][] keyBytes = ConvertUtil.toArray(keySerializer, keys);
        return this.stringRedisTemplate.execute(
                new RedisCallback<Long>() {
                    @Override
                    public Long doInRedis(RedisConnection connection) throws DataAccessException {
                        return connection.bitOp(
                                RedisStringCommands.BitOperation.NOT,
                                keySerializer.serialize(storeKeyCase),
                                keyBytes
                        );
                    }
                }, true);
    }

    /**
     * 移除字符串
     * <p>DEL key [key ...]</p>
     * @since redis 1.0.0
     * @param keys 键
     */
    public void remove(String ...keys) {
        this.stringRedisTemplate.opsForValue().getOperations().delete(Arrays.asList(keys));
    }

    /**
     * 获取spring string redis模板
     * @return 返回字符串模板
     */
    public StringRedisTemplate getStringRedisTemplate() {
        return this.stringRedisTemplate;
    }
}
